package com.franklin.java.action.sql.entity.ui;

import com.flychord.jdk.util.matchcase.TupleCase;
import com.flychord.jdk.util.tuple.Tuple;
import com.flychord.jdk.util.tuple.Tuple2;
import com.flychord.jdk.util.tuple.Tuple3;
import com.franklin.java.api.ui.tree.BaseSimpleNode;
import com.franklin.java.configuration.sql.completion.CompletionSettingState;
import com.franklin.java.configuration.sql.completion.TableNamePrefixState;
import com.franklin.java.configuration.sql.type.TypeMappingState;
import com.franklin.java.entity.sql.ColumnInfo;
import com.franklin.java.entity.sql.MappingType;
import com.franklin.java.utils.EntityClassHelper;
import com.franklin.java.utils.PsiUtils;
import com.franklin.java.utils.SqlHelper;
import com.franklin.plugins.common.collection.PrettyArrayList;
import com.franklin.plugins.common.utils.DataCheckUtils;
import com.franklin.plugins.common.utils.StringHelper;
import com.google.common.collect.Lists;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.NlsSafe;
import com.intellij.psi.PsiClass;
import com.intellij.psi.xml.XmlTag;
import com.intellij.ui.treeStructure.SimpleNode;

import java.util.*;
import java.util.stream.Collectors;

import static com.flychord.jdk.util.tuple.TupleAble.tuple;
import static com.franklin.java.action.sql.entity.ui.GenerateEntityClassForm.prefixTab;

/**
 * ResultMap节点抽象父类
 *
 * @author Franklin
 * @since 2021/9/13 14:33
 */
public abstract class ResultMapChildNode extends BaseSimpleNode<ResultMapChildNode> {

    protected final String xmlNameBase = "<%s %s>";

    protected String name;


    /**
     * xml属性
     */
    protected List<Tuple2<String, String>> properties = new PrettyArrayList<>();


    protected ResultMapChildNode(SimpleNode aParent) {
        super(aParent);
    }

    @Override
    public List<ResultMapChildNode> getChildrenNodeList() {
        if (DataCheckUtils.isEmpty(childrenNodeList)) {
            return new PrettyArrayList<>();
        }
        //按照Mybatis节点顺序排序
        Map<String, List<ResultMapChildNode>> tagNameChildNodesMap = childrenNodeList.
                stream()
                .collect(Collectors.groupingBy(ResultMapChildNode::getTagName));

        List<String> childNodeNames = this.getChildNodeNames();
        List<ResultMapChildNode> result = new PrettyArrayList<>();
        childNodeNames.forEach(childNodeName -> {
            List<ResultMapChildNode> childNodeList = tagNameChildNodesMap.getOrDefault(childNodeName, Collections.emptyList());
            result.addAll(childNodeList);
        });
        this.childrenNodeList = result;

        return result;
    }

    public List<String> getChildNodeNames() {
        return Lists.newArrayList(
                "constructor",
                "id", "result", "association",
                "collection", "discriminator"
        );
    }

    protected List<ResultMapChildNode> resolveColumnInfos(Project project, List<ColumnInfo> columnInfoList) {
        List<ResultMapChildNode> resultNodes = columnInfoList.stream()
                .map(columnInfo -> {
                    if (columnInfo.isId()) {
                        return new ResultNode.IdNode(this, "id", columnInfo.getColumnName());
                    }
                    String alias = columnInfo.getAlias();
                    String columnName = columnInfo.getColumnName();
                    if (DataCheckUtils.isNotEmpty(alias)) {
                        columnName = alias;
                    }
                    String property = columnName;
                    if (CompletionSettingState.isUseCamel(project)) {
                        property = StringHelper.underlineToCamel(columnName);
                    }
                    return new ResultNode(this, property, columnName);
                })
                .collect(Collectors.toList());
        return resultNodes;
    }

    @Override
    protected SimpleNode[] buildChildren() {
        return childrenNodeList.toArray(new SimpleNode[childrenNodeList.size()]);
    }

    @Override
    public String getName() {
        List<Tuple2<String, String>> properties = this.properties;
        String propString = properties.stream()
                .filter(
                        TupleCase.matchP(
                                (key, value) -> "property".equals(key) && DataCheckUtils.isNotEmpty(value)
                        )
                )
                .map(
                        TupleCase.match(
                                (key, value) -> key + '=' + '"' + value + '"'
                        )
                )
                .collect(Collectors.joining(" "));
        return String.format(xmlNameBase, getTagName(), propString);
    }

    /**
     * xml标签名<br>
     *
     * @param :
     * @return java.lang.String
     * @author Franklin
     * @since 2021/9/13 14:51
     */
    protected String getTagName() {
        String simpleName = this.getClass().getSimpleName();
        simpleName = simpleName.replace("Node", "");
        return StringHelper.firstToLowerCase(simpleName);
    }

    protected abstract void setProperties();

    public List<Tuple2<String, String>> getProperties() {
        return properties;
    }

    public String getProperty(String propertyName) {
        for (Tuple2<String, String> property : properties) {
            if (property._1().equals(propertyName)) {
                return property._2();
            }
        }
        return "";
    }

    public void setProperty(String propertyName, String propertyValue) {
        removeProperty(propertyName);
        properties.add(tuple(propertyName, propertyValue));
    }

    public void removeProperty(String propertyName) {
        properties.removeIf(
                TupleCase.matchP(
                        (k, v) -> k.equals(propertyName)
                )
        );
    }

    /**
     * 是否是单标签<br>
     *
     * @param :
     * @return boolean
     * @author Franklin
     * @since 2021/9/13 14:54
     */
    public abstract boolean isSingleTag();

    public static ResultMapChildNode ofValue(ResultMapChildNode parent, String nodeName) {
        ResultMapChildNode resultMapChildNode = null;
        switch (nodeName) {
            case "constructor":
                resultMapChildNode = new ConstructorNode(parent);
                break;
            case "idArg":
                resultMapChildNode = new ConstructorNode.IdArgNode(parent, "column", "javaType", "");
                break;
            case "result":
                resultMapChildNode = new ResultNode(parent, "property", "column");
                break;
            case "association":
                resultMapChildNode = new AssociationNode(parent, "property", "column", "javaType", "");
                break;
            case "id":
                resultMapChildNode = new ResultNode.IdNode(parent, "property", "column");
                break;
            case "collection":
                resultMapChildNode = new CollectionNode(parent);
                break;
            case "discriminator":
                resultMapChildNode = new DiscriminatorNode(parent, "javaType", "column");
                break;
            case "case":
                resultMapChildNode = new DiscriminatorNode.CaseNode(parent, "value", "resultType", "");
                break;
        }
        if (Objects.nonNull(resultMapChildNode)) {
            resultMapChildNode.setChildrenNodeList(new PrettyArrayList<>());
        }
        return resultMapChildNode;
    }

    /**
     * 实体类字段<br>
     *
     * @param columnNameColumnsMap:列名   - 列集合
     * @param tableNameTableCommentMap: 表名 - 表描述
     * @param importSet:                导包集合
     * @return com.flychord.jdk.util.tuple.Tuple3<java.lang.String, java.lang.String, java.lang.String>
     * @author Franklin
     * @since 2021/9/17 11:05
     */
    public Tuple3<String, String, String> getJavaField(
            Map<String, List<ColumnInfo>> columnNameColumnsMap,
            Map<String, String> tableNameTableCommentMap,
            Set<String> importSet
    ) {
        List<Tuple2<String, String>> properties = this.getProperties();
        StringBuilder fieldBuilder = new StringBuilder();
        Map<String, List<String>> keyValuesMap = properties.stream()
                .collect(Collectors.groupingBy(
                        Tuple2::_1,
                        Collectors.mapping(Tuple2::_2, Collectors.toList()))
                );
        List<String> propertyList = keyValuesMap.get("property");
        List<String> typeList = keyValuesMap.get(getJavaTypePropertyName());
        List<String> columnList = keyValuesMap.get("column");
        String field = DataCheckUtils.isEmpty(propertyList) ? "franklin" : propertyList.get(0);
        String column = DataCheckUtils.isEmpty(columnList) ? field : columnList.get(0);
        List<ColumnInfo> columnInfos = columnNameColumnsMap.get(column);
        String defaultJavaType = getDefaultJavaType();
        String type = defaultJavaType;
        String comment = "";
        MappingType mappingType = TypeMappingState.getMappingType(project);
        if (DataCheckUtils.isNotEmpty(columnInfos)) {
            ColumnInfo columnInfo = columnInfos.get(0);
            String javaType = SqlHelper.resolveJavaType(columnInfo.getColumnType(), mappingType);
            importSet.add(javaType);
            type = javaType;
            comment = columnInfo.getComment().replaceAll("db:", "").replaceAll("entity:", "");
        }
        type = DataCheckUtils.isEmpty(typeList) ? type : typeList.get(0);
        if (type.equals(defaultJavaType)) {
            importSet.add(defaultJavaType);
        }

        String qualifiedName = type;

        type = StringHelper.getSimpleName(qualifiedName);

        String tableName = StringHelper.getTableName(type);
        String[] prefixes = TableNamePrefixState.getPrefix(project).split(";");
        for (String prefix : prefixes) {
            String tableNameWithPrefix = prefix + "_" + tableName;
            comment = tableNameTableCommentMap.getOrDefault(tableNameWithPrefix, comment);
        }

        String genericType = getGenericType();
        if (DataCheckUtils.isNotEmpty(genericType)) {
            importSet.add(genericType);
            String genericSimple = StringHelper.getSimpleName(genericType);
            type = genericSimple + '<' + type + '>';
        }

        if (DataCheckUtils.isNotEmpty(comment)) {
            fieldBuilder
                    .append(prefixTab).append("/**\n")
                    .append(prefixTab).append(" * ").append(comment).append('\n')
                    .append(prefixTab).append(" */").append('\n');
        }

        fieldBuilder
                .append(prefixTab).append("private ").append(type).append(' ').append(field).append(';').append('\n');
        return tuple(fieldBuilder.toString(), qualifiedName, field);
    }

    /**
     * get/set方法<br>
     *
     * @param selectItems:
     * @param field:
     * @param type:
     * @param className:
     * @return java.lang.String
     * @author Franklin
     * @since 2021/9/17 10:00
     */
    public String getJavaFieldMethods(List<String> selectItems, String type, String field, String className) {
        StringBuilder methodBuilder = new StringBuilder();
        String simpleName = StringHelper.getSimpleName(type);
        String upperField = StringHelper.firstToUpperCase(field);
        for (String selectItem : selectItems) {
            if (selectItem.equals("getter")) {
                methodBuilder
                        .append(prefixTab).append("public ").append(simpleName).append(' ')
                        .append("get").append(upperField).append("(){").append('\n')
                        .append(prefixTab).append(prefixTab).append("return this.").append(field).append(';').append('\n')
                        .append(prefixTab).append('}').append('\n');

                methodBuilder.append('\n');
            } else if (selectItem.equals("setter")) {
                methodBuilder
                        .append(prefixTab).append("public ").append(StringHelper.getSimpleName(className)).append(' ')
                        .append("set").append(upperField)
                        .append('(').append(simpleName).append(' ').append(field).append(')').append("{\n")
                        .append(prefixTab).append(prefixTab).append("this.").append(field).append(" = ").append(field).append(";\n")
                        .append(prefixTab).append(prefixTab).append("return this").append(";\n")
                        .append(prefixTab).append("}\n");

                methodBuilder.append('\n');
            }
        }
        return methodBuilder.toString();
    }

    public String otherImport(String path) {
        String javaType = getProperty(getJavaTypePropertyName());
        if (DataCheckUtils.isEmpty(javaType)) {
            return "";
        }
        PsiClass entityClass = PsiUtils.findClassByQualifiedOrSimpleName(project, path, javaType);
        if (Objects.nonNull(entityClass)) {
            return entityClass.getQualifiedName();
        }
        return javaType;
    }

    public XmlTag parseXmlTag(XmlTag parentTag) {

        XmlTag childTag = parentTag.createChildTag(getTagName(), "", "", false);

        //设置属性
        List<Tuple2<String, String>> properties = this.getProperties();
        properties.forEach(
                TupleCase.match(
                        (key, value) -> {
                            if (DataCheckUtils.isNotEmpty(key) && DataCheckUtils.isNotEmpty(value)) {
                                childTag.setAttribute(key, value);
                            }
                        }
                )
        );

        if (isSingleTag()) {
            childTag.collapseIfEmpty();
        } else {
            //子节点
            List<ResultMapChildNode> childrenNodeList = this.getChildrenNodeList();
            for (ResultMapChildNode childNode : childrenNodeList) {
                XmlTag childSubTag = childNode.parseXmlTag(childTag);
                childTag.addSubTag(childSubTag, false);
            }
        }

        return childTag;
    }

    /**
     * 类成员<br>
     *
     * @param :
     * @param className
     * @return java.lang.String
     * @author Franklin
     * @since 2021/9/17 10:36
     */
    public String classMember(String className, Set<String> importSet) {
        return "";
    }

    protected String getDefaultJavaType() {
        return "java.lang.String";
    }

    protected String getJavaTypePropertyName() {
        return "javaType";
    }

    protected String getGenericType() {
        return "";
    }
}
